package elasticsearch

import (
	"fmt"
	es5 "github.com/elastic/go-elasticsearch/v5"
	es5api "github.com/elastic/go-elasticsearch/v5/esapi"
	es6 "github.com/elastic/go-elasticsearch/v6"
	es6api "github.com/elastic/go-elasticsearch/v6/esapi"
	es7 "github.com/elastic/go-elasticsearch/v7"
	es7api "github.com/elastic/go-elasticsearch/v7/esapi"
	jsoniter "github.com/json-iterator/go"
	"io"
	"net/http"
)

type Request struct {
	Index string
	Body  io.Reader
}

type Response struct {
	Hits         Hits                           `json:"hits"`
	Aggregations map[string]jsoniter.RawMessage `json:"aggregations"`
}

type Hits struct {
	Total int64               `json:"total"`
	Hits  jsoniter.RawMessage `json:"hits"`
}

type Error struct {
	Type   string `json:"type"`
	Reason string `json:"reason"`
	Status int    `json:"status"`
}

func (e Error) Error() string {
	return fmt.Sprintf("%s %s: %s", http.StatusText(e.Status), e.Type, e.Reason)
}

type ClientV5 es5.Client

func (c *ClientV5) ExSearch(r *Request) (*Response, error) {
	return c.parse(c.Search(c.Search.WithIndex(r.Index), c.Search.WithBody(r.Body)))
}
func (c *ClientV5) parse(resp *es5api.Response, err error) (*Response, error) {
	if err != nil {
		return nil, fmt.Errorf("error getting response: %s", err)
	}
	defer resp.Body.Close()
	if resp.IsError() {
		return nil, fmt.Errorf(resp.String())
	}
	var r struct {
		Hits struct {
			Total int64               `json:"total"`
			Hits  jsoniter.RawMessage `json:"hits"`
		} `json:"hits"`
		Aggregations map[string]jsoniter.RawMessage `json:"aggregations"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return nil, fmt.Errorf("error parsing the response body: %s", err)
	}
	return &Response{
		Hits:         Hits{Total: r.Hits.Total, Hits: r.Hits.Hits},
		Aggregations: r.Aggregations,
	}, nil
}
func (c *ClientV5) Version() (string, error) {
	res, err := c.Info()
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	if res.IsError() {
		return "", fmt.Errorf(res.String())
	}
	var r map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		return "", fmt.Errorf("error parsing the response body: %s", err)
	}
	return fmt.Sprintf("%s", r["version"].(map[string]interface{})["number"]), nil
}

type ClientV6 es6.Client

func (c *ClientV6) ExSearch(r *Request) (*Response, error) {
	return c.parse(c.Search(c.Search.WithIndex(r.Index), c.Search.WithBody(r.Body)))
}
func (c *ClientV6) parse(resp *es6api.Response, err error) (*Response, error) {
	if err != nil {
		return nil, fmt.Errorf("error getting response: %s", err)
	}
	defer resp.Body.Close()
	if resp.IsError() {
		return nil, fmt.Errorf(resp.String())
	}
	var r struct {
		Hits *struct {
			Total int64               `json:"total"`
			Hits  jsoniter.RawMessage `json:"hits"`
		} `json:"hits"`
		Aggregations map[string]jsoniter.RawMessage `json:"aggregations"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return nil, fmt.Errorf("error parsing the response body: %s", err)
	}
	return &Response{
		Hits:         Hits{Total: r.Hits.Total, Hits: r.Hits.Hits},
		Aggregations: r.Aggregations,
	}, nil
}

type ClientV7 es7.Client

func (c *ClientV7) ExSearch(r *Request) (*Response, error) {
	return c.parse(c.Search(c.Search.WithIndex(r.Index), c.Search.WithBody(r.Body)))
}
func (c *ClientV7) parse(resp *es7api.Response, err error) (*Response, error) {
	if err != nil {
		return nil, fmt.Errorf("error getting response: %s", err)
	}
	defer resp.Body.Close()
	if resp.IsError() {
		return nil, fmt.Errorf(resp.String())
	}
	var r struct {
		Hits *struct {
			Total struct {
				Value int64 `json:"value"`
			} `json:"total"`
			Hits jsoniter.RawMessage `json:"hits"`
		} `json:"hits"`
		Aggregations map[string]jsoniter.RawMessage `json:"aggregations"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return nil, fmt.Errorf("error parsing the response body: %s", err)
	}
	return &Response{
		Hits:         Hits{Total: r.Hits.Total.Value, Hits: r.Hits.Hits},
		Aggregations: r.Aggregations,
	}, nil
}

type client interface {
	ExSearch(r *Request) (*Response, error)
}
